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.

Every interaction in HNU-TimeLetter is designed around the ritual of receiving and opening a personal letter. Users do not “navigate to content” — they unseal, unfold, and explore. This document specifies the full interaction model: the envelope-opening ceremony, the dual desktop/mobile experiences, the scroll-down exhibition corridor, and the persistent global navigation bar, complete with Zustand state transitions, animation parameters, and implementation notes.

1. Envelope Opening Ritual

The homepage (/) opens with a full-screen envelope composition. The act of clicking the wax seal is the site’s primary entry ritual.

1.1 Visual Layout

The opening screen is based on a five-column vertical grid:
  • Column 1 (right edge) — Red ribbon, full viewport height, serves as the left visual anchor.
  • Column 2 — Vertical title “与她的海大时光笺”, stacked alongside the ribbon.
  • Columns 3–5 (center) — The envelope hero, with the wax seal rendered in a physical-material style at fixed dimensions.

1.2 Click Sequence

1

Wax seal crack

User clicks the envelope. The wax seal plays a cracking animation. No state change yet — this is a pure micro-animation.
2

Cover flips open

The envelope flap lifts and rotates open. Framer Motion delays the zIndex drop on the cover from 300 by 0.15 s to prevent the cover from overlapping the letter during the next phase.
3

Letter unfolds and rises

The letter paper slides upward out of the envelope. Zustand transitions to the opening stage. The title simultaneously slides upward with a light blur-fade-out.
4

Letter fills the viewport

The letter expands from its resting position to cover the full viewport. isTransitioning is set to true, activating the transition overlay before the letter reaches full size.
5

Transition overlay fades out

The full-screen transition mask fades out. isEnvelopeOpened is set to true. The page component then schedules a short timeout to dismiss the overlay.
6

Main experience revealed

The overlay is dismissed. The main experience appears — the interactive map on desktop or the stamp-collector feed on mobile, depending on the user’s device.

1.3 Zustand State Transitions

// Simplified Zustand store shape
interface EnvelopeStore {
  stage: 'idle' | 'opening' | 'done'
  isTransitioning: boolean
  isEnvelopeOpened: boolean
}

// Transition sequence
// 1. User click  →  stage = 'opening'
// 2. Letter begins expanding  →  isTransitioning = true
// 3. Animation complete  →  isEnvelopeOpened = true
// 4. Page layer timeout  →  overlay dismissed

1.4 Entry Animations

ElementTriggerDurationEasingTechnique
Red ribbonPage first load1400 mscubic-bezier(0.16, 1, 0.3, 1)clip-path vertical reveal, top → bottom; double requestAnimationFrame trigger; no position/size change
EnvelopePage first load (simultaneous with ribbon)SpringSpring physicsFalls from above, settles into gentle idle float
TitleAfter ribbon animation completes~300 msopacity fadeFades in; fades out + upward shift when opening begins
Wax sealIdle stateFixed -5 deg tilt, no hover/tap deformation
The ribbon uses clip-path reveal rather than translateY so that the ribbon’s visual footprint in the grid does not shift during the entrance. The double requestAnimationFrame ensures the initial clip-path value is painted before the animation begins, preventing a flash of the final state.

1.5 Scroll Strategy (Pre-opening State)

While the envelope has not been opened, the homepage extends downward into the scroll-down exhibition pages (About Project → About Us → Credits → Footer). Desktop uses Lenis for smooth inertial scrolling (hides the native scrollbar); mobile keeps native vertical scrolling. After the opening sequence completes, the entire page switches to the map / stamp-feed experience.

2. Responsive Strategy

HNU-TimeLetter uses a dual-experience approach rather than simple CSS responsive scaling.
ViewportExperienceRoute implementation
Desktop (> 768 px)時光画卷 — Interactive Mapapp/page.tsx + app/map/page.tsx (desktop component tree)
Mobile (≤ 768 px)掌心集邮册 — Stamp Collector Feedapp/page.tsx + app/map/page.tsx (mobile component tree)
Conditional rendering happens inside app/page.tsx and app/map/page.tsx based on a screen-width check. The two branches are entirely separate component trees — no shared layout that merely hides/shows.

3. Desktop Map Experience (時光画卷)

The desktop experience metaphor is a horizontally-unrolling timeline scroll — immersive, dynamic, narrative.

3.1 States

StateVisualTrigger
IdleFull-screen campus map with pin pointsDefault after opening / after map unrolls
RollingMap rolls rightward into 56 px scroll stripUser clicks a location pin
Rolled56 px scroll strip on right; story stage visibleMap animation completes
UnrollingStory stage exits; map expands left back to full widthUser clicks the scroll strip

3.2 Rolling Interaction

1

Pin click

User clicks a location pin on the map. The selected location is stored in Zustand. Rolling animation begins immediately.
2

Map rolls right

The map container uses overflow-hidden. The inner map (fixed at 100vw) is right-anchored, so as the container narrows, the pin coordinates remain visually accurate. The container animates from 100vw to 56px.
3

Story stage appears

Only after the map has fully collapsed to the scroll strip (rolled state) does the story stage begin its entrance — a Spring animation from below, no internal delay. This sequencing ensures the two animations never overlap.

3.3 Story Stage

  • Card stack: The story stage centres a draggable card stack. Drag left or right to fly a card out; the stack loops infinitely.
  • Layering: The story stage is z-index: 30, sitting above the map/scroll-strip container (z-index: 10). Cards can freely overlap the scroll strip during drag without clipping.
  • Scroll strip: Always visible on the right as a persistent return-to-map entry point.

3.4 Unrolling Interaction

1

Strip click

User clicks the 56 px scroll strip. State transitions to unrolling.
2

Story stage exits

The story stage flies upward and fades out. The map does not begin expanding yet.
3

Map unrolls

Only after the story stage has completely exited does the map container expand leftward back to 100% width.
4

Idle restored

State resets to idle. The active location is cleared.

3.5 Scroll Strip Visual Reference

The 56 px strip uses a warm-parchment gradient, an outer drop shadow, and a subtle inner edge shadow. See the Visual Spec for the exact CSS token values.

4. Mobile Stamp Feed (掌心集邮册)

The mobile experience metaphor is a stamp collector’s album — efficient, intimate, readable.

4.1 Story Feed

  • Vertical waterfall of story cards — no interactive map component loaded on mobile.
  • Each card shows a high-resolution thumbnail + location name + character info.
  • FAB (Floating Action Button): Fixed bottom-right. Tapping it opens a static campus map image (view-only, not interactive) in a modal.

4.2 Detail Modal

Tapping a feed card triggers a shared element transition: the card image expands to full-screen, then the detail modal settles into its final layout. Modal layout (top → bottom):
ZoneContents
Upper ~50 %High-resolution image with left/right navigation arrow overlays
MiddleCharacter avatar + name + role information
LowerScrollable story text

4.3 Gestures

GestureAction
Tap left/right arrowSwitch between stories at the same location
Swipe left / swipe rightSwitch to a different location
Pull down (pull-to-close)Close the detail modal and return to the feed

5. Scroll-Down Pages (下滚页面群)

Below the envelope on the homepage, before the user opens the letter, there is an exhibition corridor of four sections. These sections are accessible via normal scrolling and are replaced by the map experience after the envelope is opened.

5.1 Global Scroll System

Lenis smooth scroll (desktop only):
// Key Lenis parameters
{
  duration: 1.2,
  easing: 'expo',        // exponential easing
  wheelMultiplier: 1,
  touchMultiplier: 2,    // mobile uses native scroll, not Lenis
}
Implementation lives in src/lib/useVirtualScroll.ts and related style hooks in src/app/globals.css. @waaark/luge is co-initialized alongside Lenis (its own smooth-scroll plugin is disabled) to provide scroll-driven reveal animations. Custom scrollbar (src/components/shared/CustomScrollbar.tsx): Mounts on desktop in the pre-opening state only. Track: fixed right side, 15 px wide, transparent; thumb: 10 px wide, height calculated as vh × (vh / documentHeight), 5 px border-radius. Default style: transparent fill + 1px #c23643 outline; hover: solid #c23643 fill. Syncs strictly with lenis.on('scroll'), falls back to window.scroll if no Lenis instance.

5.2 Red Guide Line

A continuous SVG <path> spans all three content sections (About Project → About Us → Credits), acting as a visual thread connecting the narrative.
PropertyValue
Width100 px stroke
Color#c23643
Caps / joinsstroke-linecap: round, stroke-linejoin: round
z-index1 — above background, below body content
MaskSVG viewport top = P1.y − 60; bottom = credits-section bottom edge
Path control points (where ribbonX = vw × 0.2 − 50 px, aligning to the ribbon center):
P1 (ribbonX,  apTop)
P2 (ribbonX,  apTop + apH × 0.10)
P3 (19.15%vw, apTop + apH × 0.125)
P4 (107.5%vw, apTop + apH × 0.4043)
P5 (-2.19%vw, crTop + crH × 0.654)    ← diagonal leap across About Us
P6 (32%vw,    crTop + crH + 80)        ← tail buried under footer boundary
Animation logic — non-reversible, y-aware progress:
PhaseScroll rangeAnchor (viewport %)
LaunchscrollY ≤ 500 px1.0 — tip tracks viewport bottom
Mid500 px < scrollY < transitionStart0.6 — tip locked at 60 % viewport
TailscrollY ≥ 0.7 × snapScroll0.6 → 1.0 (linear blend)
SnapviewBottom ≥ creditsBottomForce 1.0
Progress is clamped with Math.max(cachedMax, current) — scrolling back up never retracts the drawn stroke.

5.3 About Project Section

Full-screen (100vh), letter-paper background. Large-title / editorial-body layout using font-serif for the heading and font-sans for the body. Wide margins, generous line-height — the text introduces the project’s shared-universe creative premise.

5.4 About Us Section

Full-screen (100vh), dual-column layout that actively routes around the diagonal guide line. Left column (~45–55 % width):
  • Fixed preamble (“关于我们 / 海带视研…”) always visible at the top.
  • Below it: the active member’s role description, cross-fading on member change (opacity fade ~300 ms).
Right column — Vertical Member Ticker:
PropertyValue
Auto-advance interval2.5 s
Spring easingstiffness: 200, damping: 30
Active item scale1.15× avatar, heavier nickname weight
Inactive itemsReduced scale + lowered opacity
HoverPauses the auto-advance timer
ClickSets that member active, resets timer
Member changes drive the left-column description update simultaneously — a dual-column reading rhythm.
The ticker uses translateY to slide the list, not scrollTop, to keep it compatible with Framer Motion’s layout animation and avoid scroll-event conflicts with Lenis.

5.5 Credits Section

Full-screen, right-aligned overall. Upper zone: credits heading + ~100-character body text. Lower zone: three rows of contributor nicknames scrolling in an infinite marquee. Each row may have a different speed or direction for visual rhythm variation. The guide line’s final segment (P5 → P6) crosses this section diagonally and is masked at the footer boundary, making the stroke appear to “arrive into” the red footer band. The footer is revealed via a fixed-position parallax effect: Footer is position: fixed at the viewport bottom; a FooterSpacer holds its height in the document flow; main content scrolls away and uncovers the footer beneath. Auto-snap (src/lib/useFooterSnap.ts): When the user is actively scrolling down and is within 38 vh of the page bottom, a LERP-driven auto-scroll (lerp: 0.16, Expo ease-out) fires once to fully reveal the footer. It does not trigger on idle or upward scroll. The snap resets when the user scrolls back up past 30 % of total scrollable distance. Footer structure (top → bottom):
1

Navigation links (red background #C23643)

Four horizontally-arranged link cards on a red background: 活动公告 (Feishu doc), QQ 活动群, B站主页, GitHub 仓库.
2

Giant HIMEMATSU logo

public/HIMEMATSU.svg stretched responsively to full site width, on the red background.
3

Sub-footer bar (red background)

Left: Copyright © 2026 HIMEMATSU. All rights reserved. — Right: 琼ICP备114514号
Footer color tokens: background #C23643, primary text #ECE9E4, secondary text #ECE9E4 at 90 % opacity.

6. Global Navigation Bar

The navigation bar is a fixed, vertically-oriented pill capsule anchored at the top-right of the viewport, present across all pages.

6.1 Visual Form

PropertyValue
Positionfixed, top-right viewport, z-index above page content, below modals
ShapeVertical pill (width constant, height grows with items)
BackgroundFrosted glass — backdrop-filter: blur + semi-transparent Surface base
Border1px solid #c23643 — the red border is the emotional signature
Border radiusFull capsule (border-radius: 50 % of width)
Widthclamp(44px, 4vw, 64px) — constant across all pages and routes
The pill container’s width and height are immutable — no page customization may resize the capsule itself. Per-page visual identity is expressed only through decorative layers outside the capsule (e.g., the homepage red block below it).

6.2 Navigation Items

Three items, fixed top-to-bottom order:
LabelRouteDescription
主页/Envelope opening screen and scroll-down pages
地图/mapInteractive map main experience
公示板/creationCommunity creation board
  • Text direction: writing-mode: vertical-rl
  • Font: ChillDINGothic_SemiBold (heading font)
  • Unselected color: #5a4748 (Ink), transitions toward Ink Muted on hover
  • Selected color: #ffffff against the red active capsule

6.3 Selected State (Layout Animation)

The active item has a solid red inner capsule (#c23643) that is smaller than the outer pill — left and right gaps preserve the “pill inside a pill” visual hierarchy. Switching routes causes this inner capsule to slide smoothly to the new active position via Framer Motion layout animation. No hard-cut.

6.4 Homepage Variant

On the homepage (/) at scrollY === 0, a red rectangle (#c23643) is appended directly below the nav pill:
PropertyValue
Height~30 vh
WidthSame as the nav pill (~7 vw)
Corner radiiOnly bottom-left is rounded; top-left, top-right, bottom-right are square
PurposeEchoes the full-height red ribbon on the left, reinforcing the visual frame
As the user scrolls down, this block fades out smoothly. The nav pill returns to its standard frosted-glass + red-border form.

6.5 Announcement Button

Directly below the nav pill (gap: 0.5 rem), a circular button is always present:
PropertyValue
ShapeCircle, diameter = nav pill width
BackgroundFrosted glass + rgba(246, 241, 235, 0.55) + 1px solid #c23643
HoverBackground fills to solid #c23643; text switches from #563B3B to #ffffff; transition 0.2 s
Text公告, vertically centered, heading font, letter-spacing: 0.18em
ActionOpens https://himematsu.feishu.cn/docx/EbsDdehuLo1801xBzb1cxzJLnHb in a new tab
Reduced-motionHover state switches instantly, no transition

7. Additional Routes

RoutePurposeData strategy
/Envelope ritual + scroll-down exhibition corridorStatic / client state
/mapDesktop interactive map + mobile stamp feedFeishu-synced local static data
/creationCommunity galgame scene proposalsFeishu-synced local static data (creation-board.json)
Both /map and /creation use the same architecture: Feishu data is synced to local JSON files; pages render purely from that local data with no runtime Feishu API calls.

Build docs developers (and LLMs) love